(English follows Chinese)
過長參數列(Long Parameter List)氣味是當函式(Function)或方法(Method)擁有了太多參數。經驗上來說,當參數超過三或四個以上,我們應該開始懷疑是不是可以更簡化。
這個氣味非常符合「臃腫怪(Bloaters)」的典型,也就是程式大小超過理想的尺寸,而我們的理想是「合理的」越小越好。因此太長的方法、太大的類別、太多參數列表,都是我們想要重構的目標。
行筆至此,突然覺得上一個介紹的氣味「基本型別偏執(Primitive Obsession)」會歸類在Bloaters之下,是一件有點奇怪的事。但因為偏離本篇主題,只能先把這個疑問暫且擱下。
Bloaters 氣味都有著相似的理由,具體有以下的缺點:
介紹完三種不同氣味後,終於有第一個氣味在對照表上只存在三種重構手法對應,並且其中兩種在前面的氣味重構中已經介紹過,裏面只有一種新重構技巧。
其實對照表上不同氣味能夠對應的重構技巧多與少的落差極大,最少的氣味只有對應一種重構技巧,多則可以高達14種技巧之多。這樣巨大的落差在盡量想平均每日文章產出量的情況下相當為難,不得不動態調整文章編排。
根據對照表,可以對「應過長參數列」氣味的重構技巧為下列前三項:
人有時候是這樣,太多的時候想減少,太少的時候想增加。這三個重構技巧都屬於重構分類中的「簡化方法呼叫(Simplifying Method Calls)」,而這個分類的目標顧名思義,就是在方法呼叫時(Method Calling)能夠更簡單明瞭,其中自然包括多種簡化參數的方法。換言之,除了對照表所列出的三種技巧,實際上我認為還有其他重構手法也可以消除這個氣味。
考慮到這些以後,我在原本的列表上補上我認為也可以消除這個氣味的方法,以粗體加上星號(*)表示。經過這次系列寫作,能夠參考對照表而去修訂增補是很大的成就感,也希望我能對社群提供一些貢獻。
對照表上的原始名稱為「Replace Parameter with Method」,我依照Refactoring Guru的版本加上「Call」,更符合「方法呼叫」的原意。
這個重構手法是將原本在方法調用之前需要進行的參數準備工作搬移到方法內,藉此來移除不需要的方法參數。讓我們直接看範例:
int basePrice = quantity * itemPrice;
double seasonDiscount = this.getSeasonalDiscount();
double fees = this.getFees();
double finalPrice = discountedPrice(basePrice, seasonDiscount, fees);
此處我們需要處理的方法為 discountedPrice,具有三個參數分別是basePrice、seasonDiscount與fees。但我們可發現其中seasonDiscount與fees都只是其他方法的回傳值,並沒有在調用方法階段有其他邏輯互動。這樣的情況下,我們可以將seasonDiscount與fees搬進discountedPrice方法內。
int basePrice = quantity * itemPrice;
double finalPrice = discountedPrice(basePrice);
在重構的範例中,我們知道調用前唯一需要準備的參數只有basePrice,不需要去理解seasonDiscount與fees,甚至也不需要知道其是否存在,這大幅降低了discountedPrice這個方法的使用成本。
我們可以想像discountedPrice不僅只在一個地方被呼叫。在這次重構以後,每一處方法被調用的地方,都可以減少至少兩行參數準備,減少了重複多餘的程式碼片段。
請參考「Long Method > Refactoring 如何重構Long Method」一文。
請參考「Long Method > Refactoring 如何重構Long Method」一文。
這是一個光看標題就能夠理解,讓解釋本身顯得有點多餘的重構技巧。當參數太多的時候,我們應該考慮移除沒有使用到的參數。
在不同的物件導向語言中,有時會因為繼承覆寫的緣故,會刻意留下使用不到的參數,因為參數本身是方法或函數同名時的識別之一。這個時候會建議至少將使用不到的參數變更為「_(底線)」或是加上底線前綴(如 _unusedData)。
即使是一眼看上去沒有用到的參數,要刪除前也要仔細檢查所有參數和方法的使用情境,並通過測試來確保重構後的行為一致。否則可能會踩到意想不到的地雷。這也是有些開發者寧願留著看上去用不到的程式碼片段,也不敢輕易刪除的原因,可能是曾經吃過這種大虧。
這個方法乍看之下跟上面提到的「Replace Parameter with Method Call」有點相似,但實際上完全是不同的重構技巧。
當方法內會因為參數而進行不同的業務邏輯,最好拆分為不同的方法,請見以下範例:
void setValue(String name, int value) {
if (name.equals("height")) {
height = value;
return;
}
if (name.equals("width")) {
width = value;
return;
}
Assert.shouldNeverReachHere();
}
如範例程式所示,當name的值分別等於height或是width時,會進行互不相關的兩段邏輯,這個時候應該根據條件不同拆分為兩個不同分法,重構後如下:
void setHeight(int arg) {
height = arg;
}
void setWidth(int arg) {
width = arg;
}
將一個有兩個參數的方法,拆開為兩個各有一個參數的方法,正是我們想要追求的「Make the Small Thing」的精神。多個只有單一職責的小方法,比起一個擁有許多參數、許多不相關職責的大方法來得更簡潔、更好懂。
The Long Parameter List is a code smell that occurs when a function or method has a large number of parameters. In general, if a method has more than three or four parameters, it is considered too long.
https://refactoring.guru/smells/long-parameter-list
https://refactoring.guru/replace-parameter-with-method-call